/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.cef.repository.adaptor;

import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.api.repository.DbParameter;
import com.inspur.edp.cef.api.repository.GspDbDataType;
import com.inspur.edp.cef.core.i18n.I18nResourceUtil;
import com.inspur.edp.cef.entity.UQConstraintMediate;
import com.inspur.edp.cef.entity.dependenceTemp.DataValidator;
import com.inspur.edp.cef.repository.exception.CefRepositoryException;
import com.inspur.edp.cef.repository.utils.RepositoryUtil;
import com.inspur.edp.udt.entity.IUdtData;
import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.dataaccess.DbType;
import io.iec.edp.caf.commons.exception.CAFRuntimeException;
import io.iec.edp.caf.commons.exception.ExceptionLevel;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import java.util.*;

public  class AdapterUQChecker {

    private boolean throwException;
    private EntityRelationalAdaptor adaptor;

    public AdapterUQChecker(EntityRelationalAdaptor adaptor, boolean throwException)
    {
        this(adaptor);
        this.throwException = throwException;
    }
    public AdapterUQChecker(EntityRelationalAdaptor adaptor)
    {
        this.adaptor = adaptor;
    }


    public final void checkUniqueness(ArrayList<UQConstraintMediate> mediates) {
        for(UQConstraintMediate mediate:mediates)
        {
            checkMediateUniqueness(mediate);
        }}

    private void checkMediateUniqueness(UQConstraintMediate mediate) {
//        checkUniquenessInMemory(mediate);
        checkUniquenessFromDb(mediate);
    }

    private void checkUniquenessInMemory(UQConstraintMediate mediate) {
        if(mediate==null||mediate.getParametersInfo()==null||mediate.getParametersInfo().size()<2)
            return;
        ArrayList<String> keys =new ArrayList<>(mediate.getParametersInfo().keySet());
        for (int i=0;i<keys.size()-1;i++) {
            for (int j =i+1;j<keys.size();j++) {
                checkMediateItemUQ(mediate,mediate.getParametersInfo().get(keys.get(i)),mediate.getParametersInfo().get(keys.get(j)));
            }
        }
    }

    private void checkMediateItemUQ(UQConstraintMediate mediate, HashMap<String, Object> source, HashMap<String, Object> target) {
        if(source==null||target==null)
            return;
        if(isQqueals(source,target)==false)
            return;
        throwUQException(mediate);
    }

    private void throwUQException(UQConstraintMediate mediate) {
        String message = "";
        String language= CAFContext.current.getLanguage();
        if(throwException) {
            if (mediate.getMessage() == null || "".equals(mediate.getMessage())) {
                message = I18nResourceUtil
                        .getResourceItemValue("pfcommon", "cef_exception.properties",
                                "Gsp_Cef_UQVal_0001");

            } else {
                message =mediate.getMessage();

            }
            //throw new CefException("0001",message,null, ExceptionLevel.Info);
            throw new CefRepositoryException("0001", message, null, ExceptionLevel.Warning);
        } else {
            mediate.setCheckFailed(true);
        }
    }

    private boolean isQqueals(HashMap<String, Object> source, HashMap<String, Object> target) {
        for (Map.Entry<String,Object> item:source.entrySet()) {
            if(target.containsKey(item.getKey())==false)
                return false;

            Object targetValue = target.get(item.getKey());
            if(item.getValue()==null) {
                if(targetValue !=null)
                    return false;
            } else {
                if (targetValue == null)
                    return false;
                if (item.getValue().equals(targetValue) == false)
                    return false;
            }
        }
        return true;
    }

    private void checkUniquenessFromDb(UQConstraintMediate mediate) {
        ArrayList<DbParameter> parameters=new ArrayList<>();
        ArrayList<SqlInfo> sqlInfos = getUniquenessSqls(mediate,parameters);
        if(sqlInfos == null || sqlInfos.size() == 0)
            return;
        for(SqlInfo sqlInfo : sqlInfos){
            if(sqlInfo.getSql() ==null||"".equals(sqlInfo.getSql()))
                continue;
            Query query =buildQueryManager(sqlInfo.getSql() ,sqlInfo.getParameters(),adaptor.getEntityManager());
            if(Integer.valueOf(query.getResultList().get(0).toString()) > 0){
                throwUQException(mediate);
            }
        }

    }

    private Query buildQueryManager(String sqlText, List<DbParameter> parameters,EntityManager entityManager) {
        DataValidator.checkForEmptyString(sqlText, "sqlText");
        Query query = entityManager.createNativeQuery(sqlText);
        if(parameters!=null) {
            for (int i = 0; i < parameters.size(); i++) {
                DbParameter param = parameters.get(i);
                if(param.getDataType()== GspDbDataType.DateTime){
                    query.setParameter(i,(Date)param.getValue(), TemporalType.DATE);
                }else
                {
                    query.setParameter(i, param.getValue());
                }
            }
        }
        return query;
    }

    private ArrayList<SqlInfo> getUniquenessSqls(UQConstraintMediate mediate, ArrayList<DbParameter> parameters) {
        if(mediate.getParametersInfo()==null||mediate.getParametersInfo().size()==0)
            return null;
        String sql = String.format("SELECT COUNT(1) FROM %1$s",adaptor.getTableName());
        int index =0;
        RefObject<Integer> refObject=new RefObject<>(index);
        ArrayList<SqlInfo> sqlInfos = getUQSqls(mediate,refObject,parameters);
        for(SqlInfo sqlInfo: sqlInfos){
            if(adaptor.getLogicDeleteInfo() != null && adaptor.getLogicDeleteInfo().isEnableLogicDelete()){
                String logicCondi = " and " + adaptor.getLogicDeleteInfo().getLabelId() + " = ?" + sqlInfo.getParameters().size();
                sqlInfo.getParameters().add(adaptor.buildParam(String.valueOf(sqlInfo.getParameters().size()), GspDbDataType.Char, "0"));
                sqlInfo.setSql(RepositoryUtil.FormatMuliLang(sql + String.format(" WHERE %1$s ", sqlInfo.getSql() + logicCondi)));
            }
            else {
                sqlInfo.setSql(RepositoryUtil.FormatMuliLang(sql + String.format(" WHERE %1$s ", sqlInfo.getSql())));
            }
        }
        return sqlInfos;
    }

    private ArrayList<SqlInfo> getUQSqls(UQConstraintMediate mediate, RefObject<Integer> refObject, ArrayList<DbParameter> parameters) {
        String exceptIds = getExceptIds(mediate,refObject,parameters);
        ArrayList<SqlInfo> sqlInfos = getOrConditions(mediate,refObject,parameters);
        if(exceptIds==null||"".equals(exceptIds)){
            for(SqlInfo sqlInfo: sqlInfos){
                sqlInfo.setSql(String.format(" (%1$s) ", sqlInfo.getSql()));
            }
        }
        else{
            List<String> listExceptIds = getListExceptIds(mediate,refObject,parameters);
            for(SqlInfo sqlInfo: sqlInfos){
                String sbNotIn = getUQCondition(listExceptIds);
                sqlInfo.setSql(String.format(" (%1$s)  %2$s ",sqlInfo.getSql(),sbNotIn));
//                sqlInfo.setSql(String.format(" (%1$s)  AND %2$s NOT IN (%3$s) ",sqlInfo.getSql(),adaptor.getPrimaryKey(),exceptIds.substring(0,exceptIds.length()-1)));
            }
        }
        return sqlInfos;
    }

    private String getUQCondition(List<String> exceptIds){
        //每次批量执行个数
        int batchSize = 900;
        //批次数
        int batchCount = 0;
        int count = exceptIds.size() %  batchSize;
        batchCount = count == 0 ? (exceptIds.size() /  batchSize ) :  (exceptIds.size() /  batchSize + 1);
        StringBuilder sbNotIn = new StringBuilder();
        for(int i=0; i< batchCount; i++){
            StringBuilder sb = new StringBuilder();
            int startIndex = batchSize*i;
            int endIndex = (i + 1)*batchSize -1;
            if(endIndex >= exceptIds.size()){
                endIndex = exceptIds.size() -1;
            }
            for(int index = startIndex; index <= endIndex; index++){
                sb.append("'"+exceptIds.get(index)+"',");
            }
            sbNotIn.append(String.format(" AND %1$s NOT IN (%2$s) ", adaptor.getPrimaryKey(),sb.substring(0,sb.length()-1)));
        }
        return sbNotIn.toString();
    }


    private String getExceptIds(UQConstraintMediate mediate, RefObject<Integer> refObject, ArrayList<DbParameter> parameters) {
        List<String> exceptIds = new ArrayList<>();
        exceptIds.addAll(mediate.getExceptDeleteIds());
        exceptIds.addAll(mediate.getExceptModifyIds());
        //剔除重复元素
        HashSet h = new HashSet(exceptIds);
        exceptIds.clear();
        exceptIds.addAll(h);
        StringBuilder stringBuilder=new StringBuilder();
        for(String id:exceptIds)
        {
            stringBuilder.append(String.format("'%1$s',",id));
        }
        return stringBuilder.toString();
    }

    private List<String> getListExceptIds(UQConstraintMediate mediate, RefObject<Integer> refObject, ArrayList<DbParameter> parameters) {
        List<String> exceptIds = new ArrayList<>();
        exceptIds.addAll(mediate.getExceptDeleteIds());
        exceptIds.addAll(mediate.getExceptModifyIds());
        //剔除重复元素
        HashSet h = new HashSet(exceptIds);
        exceptIds.clear();
        exceptIds.addAll(h);
        return exceptIds;
    }

    private ArrayList<SqlInfo>  getOrConditions(UQConstraintMediate mediate, RefObject<Integer> refObject, ArrayList<DbParameter> parameters) {
        ArrayList<SqlInfo> sqlInfos = new ArrayList<>();
        StringBuilder stringBuilder=new StringBuilder();

        int batchCount = 200;
        int count =mediate.getParametersInfo().size()-1;
        int j=0;
        int k=1;
        for (Map.Entry<String, HashMap<String, Object>> item:mediate.getParametersInfo().entrySet())
        {
            stringBuilder.append("(");
            HashMap<String, Object> fieldsInfos = item.getValue();
            int count2=fieldsInfos.size()-1;
            int i=0;
            for (Map.Entry<String,Object> field:fieldsInfos.entrySet())
            {
                String column = adaptor.trans2DbColumn(field.getKey());
                GspDbDataType dataType = adaptor.getContainColumns().getItem(field.getKey()).getColumnType();
                if(dataType ==GspDbDataType.Clob){
                    if(CAFContext.current.getDbType()== DbType.Oracle ||CAFContext.current.getDbType()==DbType.DM){
                        column = " to_char(substr("+ column +",0,4000)) ";
                    }else if(CAFContext.current.getDbType()== DbType.SQLServer){
                        column = "convert(nvarchar(max),"+ column +") ";
                    }
                }

                Object value =null;
                if(field.getValue() instanceof IUdtData)
                {
                    value =field.getValue();
                }
                else
                {
                    value =adaptor.getContainColumns().getItem(field.getKey()).getTypeTransProcesser().transType(field.getValue());
                }

                Object dbValue =adaptor.getPropertyChangeValue(field.getKey(),value);

                if(dbValue==null) {
                    stringBuilder.append(String.format("  %1$s is null", column));
                }
                else
                {
                    if("".equals(dbValue) &&(CAFContext.current.getDbType()==DbType.Oracle || CAFContext.current.getDbType()==DbType.DM)){
                        stringBuilder.append(String.format("  %1$s is null", column));
                    }else {
                        stringBuilder.append(String.format(" %1$s =?%2$s", column,String.valueOf(refObject.argvalue++ )));
                        parameters.add(adaptor.buildParam(String.valueOf(j),adaptor.getDataType(field.getKey()),dbValue));
                    }
                }
                if(i!=count2)
                {
                    stringBuilder.append(" AND ");
                }
                i++;
            }
            stringBuilder.append(")");
            if(k == batchCount){//本批次结束
                k=1;
                SqlInfo sqlInfo = new SqlInfo();
                sqlInfo.setSql((stringBuilder.toString()));
                ArrayList<DbParameter> tempParameters = new ArrayList<>();
                for(int ii=sqlInfos.size()*batchCount*fieldsInfos.size(); ii<(sqlInfos.size()+1)*batchCount*fieldsInfos.size();ii++){
                    tempParameters.add(parameters.get(ii));
                }
                sqlInfo.setParameters(tempParameters);
                sqlInfos.add(sqlInfo);

                stringBuilder.delete(0, stringBuilder.length());
                refObject.argvalue = 0;//索引重新从0开始
                j++;
                continue;
            }
            if(j!=count)
                stringBuilder.append(" OR ");
            else{
                //最后一个批次
                SqlInfo sqlInfo = new SqlInfo();
                sqlInfo.setSql((stringBuilder.toString()));
                ArrayList<DbParameter> tempParameters = new ArrayList<>();
                for(int ii=sqlInfos.size()*batchCount*fieldsInfos.size(); ii< parameters.size();ii++){
                    tempParameters.add(parameters.get(ii));
                }
                sqlInfo.setParameters(tempParameters);
                sqlInfos.add(sqlInfo);
            }
            j++;
            k++;
        }
        return sqlInfos;
    }
}

class SqlInfo{
    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public ArrayList<DbParameter> getParameters() {
        return parameters;
    }

    public void setParameters(ArrayList<DbParameter> parameters) {
        this.parameters = parameters;
    }

    private String sql;
    private ArrayList<DbParameter> parameters;
}
